package priv.wangy.micro.common.config.schedule;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 作息表、课表相关配置
 * 不可交由Nacos管理维护
 *
 * @author zhangjq
 * @since 2024-05-28
 */
@Data
@ConfigurationProperties(prefix = "schedule")
public class ScheduleProperties {

    /**
     * 学制
     */
    private int yearNum;

    /**
     * 学期
     */
    private List<TermType> termsTypes;

    /**
     * 时间段列表
     */
    private List<TimePeriod> timePeriods;

    public interface SchedItemType {
        String TCH_TASK = "00";
        String TCH_ACTV = "01";
        String CLS_DAILY = "02";
        String OTHER = "09";
    }

    /**
     * 定义学期类型
     */
    @Data
    public static class TermType {
        /**
         * 学期标识
         */
        private String key;
        /**
         * 学期名称
         */
        private String name;
        /**
         * 基础学期
         * true:由学制决定的、必然存在的、每学年有且仅有一个的学期
         * false:可在校历管理中新建、删除的、不限制数量的学期
         */
        private boolean basic;
        /**
         * 学期顺序
         * 基础学期有效
         */
        private int order;
        /**
         * 学期周数
         * 基础学期有效
         */
        private int weekNum;
        /**
         * 开始日期
         * 基础学期有效
         */
        private RefDate startDate;
        /**
         * 结束日期
         * 基础学起有效
         */
        private RefDate endDate;
    }

    /**
     * 学期相关的参考日期
     */
    @Data
    public static class RefDate {
        /**
         * 是否农历
         */
        private boolean lunar;
        /**
         * 与学年开始年份的偏差量，0或1
         */
        private int year;
        /**
         * 月份
         */
        private int month;
        /**
         * 日期
         */
        private int dayOfMonth;
        /**
         * 星期
         */
        private int dayOfWeek;
    }

    /**
     * 用于定义上午、下午等时间段
     */
    @Data
    public static class TimePeriod {
        /**
         * 时间段顺序
         * 从0开始依次递增
         */
        private int order;
        /**
         * 时间段名称
         */
        private String name;
        /**
         * 默认星期
         */
        private int[] dayOfWeek;
        /**
         * 时间段内包含的列表项
         */
        private List<SchedItem> schedItems;
    }

    /**
     * 用于定义作息时间表中的影响排课列表项
     * 不影响排课的列表项可在作息时间表管理中自定义
     */
    @Data
    public static class SchedItem {
        /**
         * 列表项顺序
         * 对应时间段内从1开始递增
         */
        private int order;
        /**
         * 默认列表项类型
         * 00:教学任务类，安排课程，在人培方案周计算周课时
         * 01:教学活动类，安排任课教师
         * 02:班级日常类，安排班主任
         * 09:其他类，不安排课程或教师
         */
        private String type;
        /**
         * 列表项名称
         */
        private String name;
        /**
         * 默认星期
         */
        private int[] dayOfWeek;
        /**
         * 默认开始时间
         */
        private String startTime;
        /**
         * 默认结束时间
         */
        private String endTime;
        /**
         * 特殊情况
         * 不符合默认列表项类型的情况
         */
        private List<Exclusion> exclusions;
    }

    /**
     * 用于定义各年级与默认配置不一致的情况
     */
    @Data
    public static class Exclusion {
        /**
         * 星期[1,7]
         */
        private int[] dayOfWeek;
        /**
         * 年级[1,yearNum]
         */
        private int[] grade;
        /**
         * 列表项类型
         */
        private String type;
        /**
         * 列表项名称
         */
        private String name;
    }

    public List<Integer> getDefaultWeeklyHours() {
        List<AtomicInteger> gradeHours = new ArrayList<>();
        for (int i = 0; i < yearNum; i++) {
            AtomicInteger gradeHour = new AtomicInteger(0);
            gradeHours.add(gradeHour);
        }
        for (TimePeriod period : this.timePeriods) {
            for (SchedItem item : period.getSchedItems()) {
                if (!SchedItemType.TCH_TASK.equals(item.getType())) {
                    continue;
                }
                int[] dayOfWeeks = period.getDayOfWeek();
                if (Objects.nonNull(item.getDayOfWeek())) {
                    dayOfWeeks = item.getDayOfWeek();
                }
                for (AtomicInteger hour : gradeHours) {
                    hour.getAndAdd(dayOfWeeks.length);
                }
                if (!CollectionUtils.isEmpty(item.getExclusions())) {
                    for (Exclusion exc : item.getExclusions()) {
                        for (int i : exc.getGrade()) {
                            gradeHours.get(i - 1).getAndAdd(-exc.getDayOfWeek().length);
                        }
                    }
                }
            }
        }
        List<Integer> integers = new ArrayList<>();
        int termNum = getBasicTerms().size();
        for (int i = 0; i < yearNum; i++) {
            for (int j = 0; j < termNum; j++) {
                integers.add(gradeHours.get(i).get());
            }
        }
        return integers;
    }

    public TermType getTermTypeByKey(String key) {
        return this.termsTypes.stream().filter(t -> Objects.equals(t.getKey(), key)).findFirst().orElse(null);
    }

    public List<TermType> getBasicTerms() {
        return this.termsTypes.stream().filter(TermType::isBasic)
                .sorted(Comparator.comparing(TermType::getOrder))
                .collect(Collectors.toList());
    }

    public int getTermNum() {
        return this.yearNum * (int) this.termsTypes.stream().filter(TermType::isBasic).count();
    }
}
